#include "skyview.h"
#include "skyview_p.h"

#include <QPainter>
#include <QtGlobal>
#include <QDebug>
#include <QtCore/qmath.h>

#include <cmath> // for fmod()

namespace {
const qreal PI = 3.14159265;
const qreal DEFAULT_FOV = 0.785;

qreal rad2hours(qreal rad) {
    return 24.0 * (rad / (2.0 * PI));
}

qreal hours2rad(qreal hours) {
    return 1.0;
}
}

SkyView::SkyView(QDeclarativeItem *parent) :
    QDeclarativeItem(parent), d(new SkyViewPrivate)
{
    setClip(true);
    setFlag(QGraphicsItem::ItemHasNoContents, false);
    d->azimuth = 0;
    for (qreal ra = 0.0; ra <= 24.0; ra += 2.0) {
        for (qreal dec = -90.0; dec <= 90.0; dec += 30.0) {
            SkyObject* o = new SkyObject;
            o->ra = ra;
            o->dec = dec;
            d->objects.push_back(o);
        }
    }
    d->fov = DEFAULT_FOV;
}

SkyView::~SkyView()
{
    foreach(SkyObject* o, d->objects) delete o;
    delete d;
}

void SkyView::paint(QPainter *painter, const QStyleOptionGraphicsItem */*option*/, QWidget */*widget*/)
{
    paintGrid(painter);
    paintObjects(painter);
}

void SkyView::paintGrid(QPainter *painter) const
{
    painter->setPen(QPen(QColor("white"), 2));
    painter->setRenderHints(QPainter::Antialiasing, true);
    // draw horizon
    int horizon_y = mapToScreenPoint(0, 0).y();
    painter->drawLine(0, horizon_y, width(), horizon_y);
    int last_visible_hour = floor(leftEdgeAzimuth());
    qDebug() << "left az" << leftEdgeAzimuth() << "h -> last hour mark" << last_visible_hour;
    int current_hour = floor(fmod(24.0 + (leftEdgeAzimuth() - viewportWidthHours()), 24.0));
    qDebug() << "wp width" << viewportWidthHours() << "h -> first hour mark" << current_hour;
    do {
        QPoint p1 = mapToScreenPoint(current_hour, -90);
        QPoint p2 = mapToScreenPoint(current_hour, 90);
        qDebug() << "for" << current_hour << "p1 =" << p1 << "p2 =" << p2;
        painter->drawLine(p1, p2);
        current_hour = (current_hour + 1) % 24;
    } while (current_hour != last_visible_hour);
}

void SkyView::paintObjects(QPainter *painter) const
{
    painter->setPen(QPen(QColor("white"), 1));
    painter->setBrush(QBrush(QColor("white")));
    foreach(const SkyObject* o, d->objects) {
        painter->drawEllipse(mapToScreenPoint(o->ra, o->dec), 5, 5);
    }
}

QPoint SkyView::mapToScreenPoint(qreal ra, qreal dec) const
{
    qreal relative_ra = azimuth() - ra; // inverted because RA increases to the left
    if (relative_ra > 12.0) relative_ra -= 24.0; // account for angles passing the 0 mark
    qreal relative_dec = dec - altitude();
    qreal rel_ra_norm = relative_ra / viewportWidthHours();
    qreal rel_dec_norm = relative_dec / viewportHeightDegrees();
    return QPoint(rel_ra_norm * width() + width() / 2, -1 * rel_dec_norm * height() + height() / 2);
}

qreal SkyView::azimuth() const
{
    return d->azimuth;
}

void SkyView::setAzimuth(qreal azimuth)
{
    // Limit to 0..23.9999...
    azimuth = fmod(24.0 + azimuth, 24.0);
    if (!qFuzzyCompare(azimuth, d->azimuth)) {
        d->azimuth = azimuth;
        emit azimuthChanged();
    }
}

qreal SkyView::fov() const
{
    return d->fov;
}

void SkyView::setFov(qreal fov)
{
    if (!qFuzzyCompare(fov, d->fov)) {
        d->fov = fov;
        emit fovChanged();
        emit viewportWidthHoursChanged();
        emit viewportHeightDegreesChanged();
    }
}

qreal SkyView::leftEdgeAzimuth() const
{
    qreal az = azimuth() + viewportWidthHours() / 2.0;
    return fmod(24.0 + az, 24.0);
}

qreal SkyView::viewportHeightDegrees() const
{
    return ((fov() / (2.0 * PI)) * static_cast<qreal>(height() / width())) * 360.0;
}

qreal SkyView::viewportWidthHours() const
{
    return (fov() / (2.0 * PI)) * 24.0;
}

qreal SkyView::altitude() const
{
    return d->altitude;
}

void SkyView::setAltitude(qreal altitude)
{
    altitude = qBound(-90.0, altitude, 90.0);
    if (!qFuzzyCompare(d->altitude, altitude)) {
        d->altitude = altitude;
        emit altitudeChanged();
    }
}
